package ift605.tp3.b.resolvers;

import ift605.tp3.utils.equations.EquationUtility;
import ift605.tp3.utils.equations.IEquation;
import ift605.tp3.utils.equations.UnknownEquationTypeException;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

public abstract class AbstractResolverAgent extends Agent
{
    private static final long serialVersionUID = 1L;
    
    protected abstract IEquation derive(IEquation eq);
    
    protected abstract String getType();
    
    protected IEquation askDerive(IEquation eq) throws UnknownEquationTypeException
    {
        final ACLMessage request = new ACLMessage(ACLMessage.REQUEST);
        request.setContent(eq.getUserReadableString());
        request.addReceiver(new AID("Dispatcher", AID.ISLOCALNAME));
        send(request);
        final ACLMessage confirmation = blockingReceive(MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.CONFIRM), MessageTemplate.MatchPerformative(ACLMessage.DISCONFIRM)));
        if (confirmation.getPerformative() == ACLMessage.DISCONFIRM)
        {
            throw new UnknownEquationTypeException();
        }
        final ACLMessage result = blockingReceive(MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.FAILURE), MessageTemplate.MatchPerformative(ACLMessage.INFORM)));
        if (confirmation.getPerformative() == ACLMessage.FAILURE)
        {
            throw new UnknownEquationTypeException();
        }
        return EquationUtility.parse(result.getContent());
    }
    
    @Override
    protected void setup()
    {
        System.out.println(getLocalName() + " is here, ready to resolve !!");
        try
        {
            final DFAgentDescription dfd = new DFAgentDescription();
            dfd.setName(getAID());
            DFService.register(this, dfd);
            
            addBehaviour(new CyclicBehaviour(this)
            {
                private static final long serialVersionUID = 1L;
                
                @Override
                public void action()
                {
                    final ACLMessage msg = receive();
                    
                    if (msg != null)
                    {
                        switch (msg.getPerformative())
                        {
                            case ACLMessage.CANCEL:
                                doDelete();
                                break;
                            case ACLMessage.REQUEST: // Yay ! We have something to do
                                final String ticket = msg.getContent();
                                try
                                {
                                    
                                    // Let's confirm to the asker
                                    final ACLMessage confirm = new ACLMessage(ACLMessage.CONFIRM);
                                    confirm.setContent(ticket);
                                    confirm.addReceiver(msg.getSender());
                                    send(confirm);
                                    
                                    final ACLMessage info = blockingReceive(MessageTemplate.MatchPerformative(ACLMessage.INFORM));
                                    final String eq = info.getContent();
                                    System.out.println("<" + getLocalName() + "> will resolve (" + ticket + "): " + eq);
                                    
                                    final IEquation equation = EquationUtility.parse(eq);
                                    final IEquation result = derive(equation);
                                    if (result == null)
                                    {
                                        throw new UnknownEquationTypeException();
                                    }
                                    
                                    final ACLMessage inform1 = new ACLMessage(ACLMessage.INFORM);
                                    inform1.setContent(ticket);
                                    inform1.addReceiver(msg.getSender());
                                    send(inform1);
                                    
                                    final ACLMessage stillUseful = blockingReceive(MessageTemplate.or(MessageTemplate.MatchPerformative(ACLMessage.AGREE), MessageTemplate.MatchPerformative(ACLMessage.REFUSE)));
                                    if (stillUseful.getPerformative() == ACLMessage.DISCONFIRM)
                                    {
                                        throw new UnknownEquationTypeException();
                                    }
                                    
                                    final ACLMessage inform2 = new ACLMessage(ACLMessage.INFORM);
                                    inform2.setContent(result.getUserReadableString());
                                    inform2.addReceiver(msg.getSender());
                                    send(inform2);
                                    
                                }
                                catch (final Exception e)
                                {
                                    final ACLMessage confirm = new ACLMessage(ACLMessage.FAILURE);
                                    confirm.setContent(ticket);
                                    confirm.addReceiver(msg.getSender());
                                    send(confirm);
                                }
                                
                                final ACLMessage ready = new ACLMessage(ACLMessage.PROPOSE);
                                ready.setContent(getType());
                                ready.addReceiver(msg.getSender());
                                send(ready);
                                
                                break;
                        }
                    }
                    else
                    {
                        // if no message is arrived, block the behaviour
                        block();
                    }
                }
            });
        }
        catch (final Exception e)
        {
            System.out.println("This is too much for this poor resolver: " + e);
            e.printStackTrace();
        }
    }
}
